package net.sf.nwn.jme3;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.nwn.jme3.SkinmeshGeomNode.VertexWeight;

import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.Control;

public class AnimationBehavior implements Control {

    private Map<String, Node> namedTransforms;
    private Map<String, EmitterBehavior> namedEmitters;
    private Map<String, ModelAnimation> animations;
    private List<String> defaultAnims;
    private float animationTimeScale = 1f;
    private ModelAnimation currentAnimation;
    private ModelAnimation nextAnimation;
    private float stage;
    private boolean loop;
    private boolean enabled = true;
    private boolean playingDefault = true;
    private float explicitAnimationStage = -1;
    private Collection<Geometry> skinNodes;
    private Map<String, Spatial> rootBones = new HashMap<String, Spatial>();

    public AnimationBehavior(Map<String, Node> aNamedTransforms, Map<String, EmitterBehavior> aNamedEmitters, Map<String, ModelAnimation> anims, List<String> aDefaultAnims, Collection<Geometry> aSkinNodes) {
        namedTransforms = aNamedTransforms;
        animations = anims;
        defaultAnims = aDefaultAnims;
        namedEmitters = aNamedEmitters;
        skinNodes = aSkinNodes;

        for (Geometry g : skinNodes) {
            SkinmeshGeomNode skin = (SkinmeshGeomNode) g.getUserData(SkinmeshGeomNode.SKIN_INFO);

            Set<String> rootBoneNames = skin.getRootBones();

            for (String name : rootBoneNames) {
                rootBones.put(name, namedTransforms.get(name));
            }
        }
    }

    public synchronized void playDefaultAnimation() {

        playingDefault = true;
        int count = defaultAnims.size();

        if (count == 0) {
            return;
        }
        count = (int) (Math.random() * count);
        playAnimation(
                (ModelAnimation) animations.get(defaultAnims.get(count)),
                false);
        return;
    }

    public void disableDefaultAnimation() {
        playingDefault = false;
    }

    public void playAnimation(String name, boolean wantLoop) {
        ModelAnimation ma = (ModelAnimation) animations.get(name);

        if (ma == null) {
            return;
        }
        playAnimation(ma, wantLoop);
    }

    public void playAnimation(ModelAnimation ma, boolean wantLoop) {

        //System.out.println("Playing animation "+ ma.getName());
        nextAnimation = ma;
        loop = wantLoop;
    }

    public List<String> getDefaultAnimations() {
        return defaultAnims;
    }

    public synchronized void setDefaultAnimations(List<String> anims) {
        defaultAnims = anims;
    }

    public Set<String> getAllAnimationNames() {
        return animations.keySet();
    }

    public Collection<ModelAnimation> getAllAnimations() {
        return animations.values();
    }

    public float getAnimationTimeScale() {
        return animationTimeScale;
    }

    public void setAnimationTimeScale(float animationTimeScale) {
        this.animationTimeScale = animationTimeScale;
    }

    public Map<String, Node> getNamedTransforms() {
        return namedTransforms;
    }

    /*
    public javax.media.j3d.BaseNode cloneNode(boolean forceDuplicate)
    {
    AnimationBehavior usc = new AnimationBehavior();
    
    usc.duplicateNode(this, forceDuplicate);
    return usc;
    }
    
    public void duplicateNode(javax.BaseNode.j3d.Node originalNode, boolean forceDuplicate)
    {
    super.duplicateNode(originalNode, forceDuplicate);
    AnimationBehavior a = (AnimationBehavior) originalNode;
    
    if (forceDuplicate)
    {
    animations = new HashMap(a.animations);
    defaultAnims = new ArrayList(a.defaultAnims);
    }
    else
    {
    animations = a.animations;
    defaultAnims = a.defaultAnims;
    }
    
    namedTransforms = new HashMap(a.namedTransforms);
    namedEmitters = new HashMap(a.namedEmitters);
    }
     */
    public void setExplicitAnimationStage(float stage) {
        explicitAnimationStage = stage;
    }

    public void disableExplicitAnimationStage() {
        explicitAnimationStage = -1;
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        // TODO Auto-generated method stub
    }

    @Override
    public void read(JmeImporter im) throws IOException {
        // TODO Auto-generated method stub
    }

    @Override
    public Control cloneForSpatial(Spatial spatial) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void setSpatial(Spatial spatial) {
        // TODO Auto-generated method stub
    }

  
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

   
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void update(float tpf) {

        updateBones();


        if (nextAnimation != null) {
            stage = 0;
            currentAnimation = nextAnimation;
            nextAnimation = null;
        }

        if (currentAnimation == null) {
            if (playingDefault) {
                playDefaultAnimation();
            }
            return;
        }

        boolean interpolate = true;

        if (explicitAnimationStage >= 0) {
            stage = currentAnimation.getLength() * explicitAnimationStage;
            interpolate = false;
        }

        currentAnimation.update(namedTransforms, namedEmitters, stage, interpolate);

        stage += tpf / animationTimeScale;

        if (explicitAnimationStage < 0 && stage >= currentAnimation.getLength()) {
            if (!loop) {
                if (playingDefault) {
                    playDefaultAnimation();
                }
                return;
            }
            nextAnimation = currentAnimation;
            return;
        }


    }

    private void updateBones() {
        for (Geometry g : skinNodes) {
            SkinmeshGeomNode skin = (SkinmeshGeomNode) g.getUserData(SkinmeshGeomNode.SKIN_INFO);
            Mesh mesh = g.getMesh();
            int vertexCount = mesh.getVertexCount();

            List<VertexWeight[]> vertexWeights = skin.getVertexWeights();

            VertexBuffer buffer = mesh.getBuffer(Type.Position);
            FloatBuffer data = (FloatBuffer) buffer.getData();

            Vector3f tmp = new Vector3f();
            for (int v = 0; v < vertexCount; v++) {
                VertexWeight[] weights = vertexWeights.get(v);
                Vector3f pos = new Vector3f();

                for (VertexWeight weight : weights) {
                    Spatial spatial = rootBones.get(weight.boneName);
                    Vector3f bonePosition = spatial.getWorldTranslation();
                    tmp.set(bonePosition);
                    tmp.multLocal(weight.weight);
                    pos.addLocal(tmp);
                }

                Vector3f original = skin.getVerts().get(v);

                Transform worldTransform = g.getWorldTransform();
                pos = worldTransform.transformInverseVector(pos, null);

                pos.addLocal(original);
                data.put(v * 3, pos.x);
                data.put(v * 3 + 1, pos.y);
                data.put(v * 3 + 2, pos.z);
            }
            buffer.updateData(data);

        }
    }

    @Override
    public void render(RenderManager rm, ViewPort vp) {
    }
}
